π§Ύ Brief Introduction to Payload CMS
Payload CMS is a Headless content management system based on Node.js and TypeScript, designed for developers. Unlike many traditional CMSs that come with pre-built UIs or themes, Payload focuses on providing a developer-centric, API-driven, and customizable experience.
π― Key Features of Payload:
- TypeScript-first structure: All structures, data models, and configurations are defined with the powerful TypeScript type system.
- Modern admin interface: Automatically generates a complete content management UI from your schema.
- Built-in security and authentication: It has a built-in, extensible authentication and access control system.
- Hooks and Plugins Support: You can control the behavior of the system precisely using hooks and plugins.
Payload allows developers to create a highly customizable, secure, and extensible content management system without being limited to predefined structures. That is why it is a popular choice for complex, custom, and modern projects.
π What is the payload-auditor
plugin?
The payload-auditor
plugin is a powerful plugin for Payload CMS that allows for precise audit of operations performed on collections. The plugin automatically records all changes such as create, update, and delete data and stores them in a structured log format.
β¨ The main goal of this plugin:
Provide a transparent and reliable logging system for monitoring user behavior and reviewing database-level change history.
βοΈ Key features:
- Automatic logging of operations on collections
- Support for defining custom logger for custom logging
- Support for all CMS payload hooks
- Modular design with extensibility for complex projects
Using this plugin, developers and system administrators can easily understand who, when, and what changes were made to the system; a vital feature for projects where security, transparency, and audit trails are of high importance. To learn more about this plugin, you can read the article Tracking and Security in Payload CMS with the Payload-Auditor Plugin.
π The importance of custom logging in content management systems (CMS)
In content management systems, especially in enterprise or sensitive projects, accurate tracking of user activities and data changes plays a very important role. While basic logging can capture general information about operations, custom logging allows for accurate, targeted, and analyzable information to be stored for the specific needs of each project.
Why do we need custom logging?
1. π― Customization
Projects are different; one project may need to record user IP, another may want to store user roles or geographic information. Custom logging allows for critical information tailored to the project scenario to be recorded.
2. π΅οΈββοΈ Transparency and Audit Trail
For systems that require user behavior to be investigated or security issues to be addressed, the complete change trail (who changed what, when, and how) must be clearly visible. This transparency cannot be achieved without accurate and customized logging.
3. π Security and Accountability
In many projects, especially in areas such as finance, medicine or government, accurate recording of activities plays an important role in data security and user accountability. Custom logs can also be used as legal evidence.
4. π Analytics and Intelligence
With rich and structured logs, you can analyze user behavior, identify process weaknesses and ultimately make more informed decisions to improve system performance.
Logging is not just a monitoring tool; it is an integral part of security, optimization and professional management of content systems. By providing the ability to record custom logs, you can have much more control and insight into what is happening in the system.
π§ What is customLogger
?
In the payload-auditor
plugin, the customLogger
feature allows developers to fully customize logging behavior at the hook level. This feature is designed for projects that need to generate or modify log content in a specific way depending on different conditions, since it is almost impossible to predict the default values for logging in the logs.
π§© Defining customLogger
at the Hook Level
customLogger
is an optional function that can be specified in the payload-auditor
plugin configuration. This function is called when the hook used for each operation is executed and can edit or overwrite the log data before final storage.
customLogger?: (
args: Parameters<AllCollectionHooks['afterChange']>[0],
fields: Omit<AuditorLog, 'hook'>,
) => Omit<AuditorLog, 'hook'> | Promise<Omit<AuditorLog, 'hook'>>
In this function:
-
args
: Contains information about the hook for the same document and can have values likereq
,operation
,doc
, etc. -
fields
: Contains the default log structure without thehook
field (because the principle of which hook created this log should not change). - The output of the function should be a final version of
AuditorLog
(except for thehook
added by the plugin).
π Multi-level customLoggers
In payload-auditor
, we have considered four levels of the customLogger function to increase flexibility.
π’ customLogger
definition levels (in order of execution priority):
1. π₯ Operation-Level
At this level, a dedicated customLogger
can be defined for each specific operation (create
, update
, delete
). This level has the highest execution priority and if it exists, no other levels will be executed.
create: {
customLogger: (args, fields) => {
return { ...fields, user: null }
},
enabled: true,
}
π Applies only to that specific operation
β Has the highest priority
2. π₯ Hook-Level
If customLogger
is not defined for a specific operation, the system looks for customLogger
at the hook level, such as afterChange
.
customLogger: (args, fields) => {
return { ...fields, user: null }
}
π Only for specific hook (e.g. afterChange)
β Not executed ifcustomLogger
is defined as an operation level
β Only works on active operations
3. π₯ All Hooks Level (Hooks-Level)
At this level, customLogger
is applied to all hooks and operations, if none of the previous two levels exist.
customLogger: (args, fields) => {
return { ...fields, type: 'error' }
}
π Only runs if there is no logger at the hook and operation level
β Only works on active operations
β Cannot be defined for a specific hook β covers the entire hook system
4. π Global-Level
If no customLogger
is defined at the operation, hook or all hooks level, then the global customLogger
is run. This level works for all collections.
customLogger: (args, fields) => {
return { ...fields, userAgent: 'custom user agent' }
}
π Last level of fallback in execution priority
β Works only on active operations
π Execution hierarchy summary:
[Highest priority] Operation β Hook β All hooks β Global [Lowest priority]
The more detailed the definition level, the higher the execution priority. This hierarchy provides great flexibility for precise, customized, and targeted logging.
𧬠customLogger
structure
The customLogger
function is the heart of the custom logging mechanism in the payload-auditor
plugin. This function allows you to edit or generate log fields before final logging. To do this, the function takes two inputs and must return a specific output.
π Function Inputs
The customLogger
function has two main inputs:
1. args
: All hook arguments
This argument contains complete information about the current hook and the operation that was executed. Typically contains values like:
-
req
: HTTP request object -
operation
: Operation type (create
,update
,delete
) -
doc
: Final document after modification -
previousDoc
: Previous document (in update and delete operations) - Other information depending on the hook type
2. fields
: Initial log structure (Log Fields)
This object contains the default log data generated by the plugin, without fields hook
. You can override, modify, or add fields to this object.
πNote: If you want to add a new field, you must define that field for the plugin root collection using
configureRootCollection
π€ Expected Output
The customLogger
function should return an object that has the following structure:
Omit<AuditorLog, 'hook'>
That is, it must include all the fields required for logging, except hook
which is automatically added by the plugin.
π Simple example:
(args, fields) => {
return {
...fields,
user: null,
ip: args.req?.ip || 'unknown',
}
}
β³ Support for async operations
The customLogger
function can also operate asynchronously (*****async
* ) . That is, you can use async functions such as accessing a database, reading from external APIs or long-running processes and return the final value with await
.
π Example with Promise:
async (args, fields) => {
const geoInfo = await getGeoInfo(args.req?.ip)
return {
...fields,
location: geoInfo.city,
}
}
π If the function output is of type
Promise
, the plugin will automatically resolve it.
This flexibility in the customLogger
structure allows you to generate the structure you want for any scenario, from simple logging to the most complex analytics.
π Practical examples
In this section, we will examine two common examples of customLogger
usage; one simple and the other with async operations. Each example is analyzed line by line to make the practical concepts clear.
1. π§Ό Changing the user value to null
customLogger: (args, fields) => {
return { ...fields, user: null };
}
π§© Line-by-line analysis
-
customLogger
: is a synchronous function that takes two parameters: -
args
: information about the operation, such as request, document, etc. -
fields
: raw log data before final processing. -
...fields
: first we keep all the initial fields of the log. -
user: null
: then we manually set the value ofuser
tonull
.
π― Real-world uses of this change
- Removal of sensitive information: In some scenarios you may not want to record user IDs or information (for example, in anonymous forms).
- Anonymization of logs for privacy or in test environments.
-
Automated processes where no human user is involved and
user
is irrelevant.
2. π Adding custom information (e.g. IP)
πNote: If you want to add a new field, you must define that field for the plugin root collection using
configureRootCollection
customLogger: async (args, fields) => {
const ip = await getIPFromRequest(args.req);
return { ...fields, meta: { ...fields.meta, ip } };
}
π§© Line-by-line analysis
- The function is
async
because it uses theawait
helper function. -
getIPFromRequest(args.req)
: A hypothetical function to extract the IP from the request. -
...fields
: Preserve the original data. -
meta: { ...fields.meta, ip }
: Add the IP to the log metadata section, without removing the previous metadata.
π§° Introducing the helper function
The getIPFromRequest
function can be defined simply as:
function getIPFromRequest(req?: PayloadRequest): string {
return req?.ip || req?.headers['x-forwarded-for']?.toString() || 'unknown';
}
This function first gets the IP from
req.ip
, otherwise it uses proxy headers.
π― Real-world applications
- Security analysis: Detect suspicious situations by examining IP.
- Recording user behavior based on geographic location.
- Monitoring system load from specific regions or specific users.
β οΈ Critical points when using customLogger
To use customLogger
correctly and effectively in the payload-auditor
plugin, it is crucial to be familiar with some limitations and prioritization behaviors. In this section, we will review the most important critical points:
π 1. Prioritization when using customLogger
If you have defined customLogger
at different levels (for example, both at the operation level and at the hook or global level), the plugin will always use the closest definition. The order of precedence is as follows:
- customLogger at the operation-level For example:
create: {
customLogger: ...
}
β Highest priority β will only be executed for that specific operation.
- customLogger at hook level For example:
afterChange: {
customLogger: ...
}
β Runs only if the corresponding operation does not have a dedicated customLogger.
- customLogger at the global hooks level For example:
hooks: {
customLogger: ...
}
β Runs if neither operation-level nor hook-level customLogger exists.
- customLogger at global level (for all collections) For example:
auditor: {
customLogger: ...
}
β Finally, the final fallback; runs only if no other level has a customLogger.
π§© 2. Differences with customLogger
at hook level
There are important differences between customLogger
at hook level (e.g. afterChange
) and operation level:
Feature | customLogger (operation) | customLogger (hook-level) |
---|---|---|
Dependent on operation type | For any operation you have defined | Yes, but only when not defined |
Scope of effect | A specific operation | All operations related to that hook |
Execution priority | Higher | Lower than operation level |
β οΈ It is important not to confuse these two levels, as you might think your customLogger is running, but because there is a more precise version at a lower level, it is not called at all.
β Best Practices
To make your logging system both fast and secure, it is very important to follow a few key principles in the design and implementation of customLogger
. In this section, we will discuss recommendations that will both improve the developer experience and make the system easier to maintain:
π§Ό 1. Keep your log structure simple
- Minimalism in logging helps reduce data volume and storage costs.
- Avoid adding unnecessary or nested fields.
- Always design logs for fast processing in analytics systems (like ELK, Datadog, or Logstash).
β
Good example:
return {
...fields,
action: 'create',
user: user.id,
};
β Bad example:
return {
...fields,
debugDump: req.body,
requestHeaders: req.headers,
timestamp: new Date(),
callStack: new Error().stack,
};
π 2. Avoid storing sensitive data (unless encrypted)
πNote: The plugin itself may store email in raw form in the logs. It is the developer's responsibility to use encryption to store sensitive data
- Avoid storing private user data such as email, IP, or phone number in raw form.
- If you need to store this data, use encryption or hashing.
β
Suggested example:
return {
...fields,
meta: {
emailHash: sha256(user.email),
},
};
π 3. Use async for external data
If you need to get data from external sources (such as IP location, browser information, or external validations), customLogger
fully supports async
functions.
Example: Adding user location from IP
customLogger: async (args, fields) => {
const location = await getGeoLocation(args.req?.ip);
return {
...fields,
meta: {
...fields.meta,
location,
},
};
}
π Note: If you use external resources:
- Pay attention to API latency.
- Consider caching or rate-limiting.
- Use appropriate timeouts so that the entire log process does not break.
π― Other recommendations
- Use collections for testing
customLogger
whose changes are less critical. - Do not use
customLogger
to modify the original data (it is only for creating logs). - If you need a different customLogger based on the user type, you can put a condition in it:
if (args.req?.user?.role === 'admin') {
return { ...fields, severity: 'high' };
}
π Conclusion
The customLogger
feature in the payload-auditor
plugin is a very powerful tool for customizing the logging process in Payload CMS-based projects. This feature allows you to precisely and with full control, tailor the content of the logs to your project's security, analytical, and operational needs.
π§© Summary of features
- High flexibility at four levels: operation level, hook, hooks-level and global
- Ability to process synchronously and asynchronously (async) in log generation
- Support for external data such as IP, geolocation or user role
- Ability to filter, modify, or remove sensitive information before saving
π― More accurate tracking of system changes
By using customLogger
wisely, you can:
- Record and analyze specific changes based on user context
- Generate targeted and actionable logs that are valuable for monitoring and security tools
- Make the debugging process faster and more accurate for the development team
π‘ Suggestions for developers
- Use
customLogger
to build a reliable logging infrastructure alongside other observability tools like Sentry Or use Grafana. - For projects with high security requirements, be sure to encrypt sensitive data in logs.
- Use async loggers to balance system performance with log richness.
Ultimately, customLogger
is not just an optional feature, but can be a key part of your audit and security strategy. If used correctly, it can be the difference between a purely decorative log and a real analysis and response tool.
π Link to the main plugin documentation
-
Official documentation
payload-auditor
Includes a full explanation of options, log structure, logging levels, and practical examples.
π» GitHub Repository Link
- GitHub Repository: π https://github.com/shaadcode/payload-auditor
In this repo you can:
- View and install the latest version of the plugin
- Track Issues and Feature Requests
- Contribute to the plugin development
π€ Appreciation and Contribution
If you found this plugin useful or this article helped you understand it better, please support me by giving the payload-auditor repo a βοΈ. This motivates me to develop more open source tools.
π Thank you for your support!
Top comments (0)