diff --git a/packages/parser/src/envelopes/eventbridge.ts b/packages/parser/src/envelopes/event-bridge.ts
similarity index 100%
rename from packages/parser/src/envelopes/eventbridge.ts
rename to packages/parser/src/envelopes/event-bridge.ts
diff --git a/packages/parser/src/middleware/parser.ts b/packages/parser/src/middleware/parser.ts
new file mode 100644
index 0000000000..2a79ad0a3f
--- /dev/null
+++ b/packages/parser/src/middleware/parser.ts
@@ -0,0 +1,56 @@
+import { type MiddyLikeRequest } from '@aws-lambda-powertools/commons/types';
+import { type MiddlewareObj } from '@middy/core';
+import { type ZodSchema } from 'zod';
+import { type Envelope } from '../types/envelope.js';
+
+interface ParserOptions<S extends ZodSchema> {
+  schema: S;
+  envelope?: Envelope;
+}
+
+/**
+ * A middiy middleware to parse your event.
+ *
+ * @exmaple
+ * ```typescript
+ * import { parser } from '@aws-lambda-powertools/parser/middleware';
+ * import middy from '@middy/core';
+ * import { sqsEnvelope } from '@aws-lambda-powertools/parser/envelopes/sqs;'
+ *
+ * const oderSchema = z.object({
+ *   id: z.number(),
+ *   description: z.string(),
+ *   quantity: z.number(),
+ * });
+ *
+ * type Order = z.infer<typeof oderSchema>;
+ *
+ * export const handler = middy(
+ *   async (event: Order, _context: unknown): Promise<void> => {
+ *     // event is validated as sqs message envelope
+ *     // the body is unwrapped and parsed into object ready to use
+ *     // you can now use event as Order in your code
+ *   }
+ * ).use(parser({ schema: oderSchema, envelope: sqsEnvelope }));
+ * ```
+ *
+ * @param options
+ */
+const parser = <S extends ZodSchema>(
+  options: ParserOptions<S>
+): MiddlewareObj => {
+  const before = (request: MiddyLikeRequest): void => {
+    const { schema, envelope } = options;
+    if (envelope) {
+      request.event = envelope(request.event, schema);
+    } else {
+      request.event = schema.parse(request.event);
+    }
+  };
+
+  return {
+    before,
+  };
+};
+
+export { parser };
diff --git a/packages/parser/src/types/envelope.ts b/packages/parser/src/types/envelope.ts
new file mode 100644
index 0000000000..e54958dca2
--- /dev/null
+++ b/packages/parser/src/types/envelope.ts
@@ -0,0 +1,29 @@
+import { type apiGatewayEnvelope } from '../envelopes/apigw.js';
+import { type apiGatewayV2Envelope } from '../envelopes/apigwv2.js';
+import { type cloudWatchEnvelope } from '../envelopes/cloudwatch.js';
+import { type dynamoDDStreamEnvelope } from '../envelopes/dynamodb.js';
+import { type kafkaEnvelope } from '../envelopes/kafka.js';
+import { type kinesisEnvelope } from '../envelopes/kinesis.js';
+import { type kinesisFirehoseEnvelope } from '../envelopes/kinesis-firehose.js';
+import { type lambdaFunctionUrlEnvelope } from '../envelopes/lambda.js';
+import { type snsEnvelope, type snsSqsEnvelope } from '../envelopes/sns.js';
+import { type sqsEnvelope } from '../envelopes/sqs.js';
+import { type vpcLatticeEnvelope } from '../envelopes/vpc-lattice.js';
+import { type vpcLatticeV2Envelope } from '../envelopes/vpc-latticev2.js';
+import { type eventBridgeEnvelope } from '../envelopes/event-bridge.js';
+
+export type Envelope =
+  | typeof apiGatewayEnvelope
+  | typeof apiGatewayV2Envelope
+  | typeof cloudWatchEnvelope
+  | typeof dynamoDDStreamEnvelope
+  | typeof eventBridgeEnvelope
+  | typeof kafkaEnvelope
+  | typeof kinesisEnvelope
+  | typeof kinesisFirehoseEnvelope
+  | typeof lambdaFunctionUrlEnvelope
+  | typeof snsEnvelope
+  | typeof snsSqsEnvelope
+  | typeof sqsEnvelope
+  | typeof vpcLatticeEnvelope
+  | typeof vpcLatticeV2Envelope;
diff --git a/packages/parser/tests/unit/envelopes/eventbridge.test.ts b/packages/parser/tests/unit/envelopes/eventbridge.test.ts
index ee8c62a95c..8212e77d9f 100644
--- a/packages/parser/tests/unit/envelopes/eventbridge.test.ts
+++ b/packages/parser/tests/unit/envelopes/eventbridge.test.ts
@@ -7,7 +7,7 @@
 import { TestEvents, TestSchema } from '../schema/utils.js';
 import { generateMock } from '@anatine/zod-mock';
 import { EventBridgeEvent } from 'aws-lambda';
-import { eventBridgeEnvelope } from '../../../src/envelopes/eventbridge.js';
+import { eventBridgeEnvelope } from '../../../src/envelopes/event-bridge.js';
 
 describe('EventBridgeEnvelope ', () => {
   it('should parse eventbridge event', () => {
diff --git a/packages/parser/tests/unit/parser.test.ts b/packages/parser/tests/unit/parser.test.ts
new file mode 100644
index 0000000000..979892a21c
--- /dev/null
+++ b/packages/parser/tests/unit/parser.test.ts
@@ -0,0 +1,123 @@
+/**
+ * Test middleware parser
+ *
+ * @group unit/parser
+ */
+
+import middy from '@middy/core';
+import { Context } from 'aws-lambda';
+import { parser } from '../../src/middleware/parser.js';
+import { generateMock } from '@anatine/zod-mock';
+import { SqsSchema } from '../../src/schemas/sqs.js';
+import { z, type ZodSchema } from 'zod';
+import { sqsEnvelope } from '../../src/envelopes/sqs';
+import { TestSchema } from './schema/utils';
+
+describe('Middleware: parser', () => {
+  type schema = z.infer<typeof TestSchema>;
+  const handler = async (
+    event: unknown,
+    _context: Context
+  ): Promise<unknown> => {
+    return event;
+  };
+
+  describe(' when envelope is provided ', () => {
+    const middyfiedHandler = middy(handler).use(
+      parser({ schema: TestSchema, envelope: sqsEnvelope })
+    );
+
+    it('should parse request body with schema and envelope', async () => {
+      const bodyMock = generateMock(TestSchema);
+      parser({ schema: TestSchema, envelope: sqsEnvelope });
+
+      const event = generateMock(SqsSchema, {
+        stringMap: {
+          body: () => JSON.stringify(bodyMock),
+        },
+      });
+
+      const result = (await middyfiedHandler(event, {} as Context)) as schema[];
+      result.forEach((item) => {
+        expect(item).toEqual(bodyMock);
+      });
+    });
+
+    it('should throw when envelope does not match', async () => {
+      await expect(async () => {
+        await middyfiedHandler({ name: 'John', age: 18 }, {} as Context);
+      }).rejects.toThrowError();
+    });
+
+    it('should throw when schema does not match', async () => {
+      const event = generateMock(SqsSchema, {
+        stringMap: {
+          body: () => '42',
+        },
+      });
+
+      await expect(middyfiedHandler(event, {} as Context)).rejects.toThrow();
+    });
+    it('should throw when provided schema is invalid', async () => {
+      const middyfiedHandler = middy(handler).use(
+        parser({ schema: {} as ZodSchema, envelope: sqsEnvelope })
+      );
+
+      await expect(middyfiedHandler(42, {} as Context)).rejects.toThrowError();
+    });
+    it('should throw when envelope is correct but schema is invalid', async () => {
+      const event = generateMock(SqsSchema, {
+        stringMap: {
+          body: () => JSON.stringify({ name: 'John', foo: 'bar' }),
+        },
+      });
+
+      const middyfiedHandler = middy(handler).use(
+        parser({ schema: {} as ZodSchema, envelope: sqsEnvelope })
+      );
+
+      await expect(
+        middyfiedHandler(event, {} as Context)
+      ).rejects.toThrowError();
+    });
+  });
+
+  describe(' when envelope is not provided', () => {
+    it('should parse the event with built-in schema', async () => {
+      const event = generateMock(SqsSchema);
+
+      const middyfiedHandler = middy(handler).use(
+        parser({ schema: SqsSchema })
+      );
+
+      expect(await middyfiedHandler(event, {} as Context)).toEqual(event);
+    });
+
+    it('should parse custom event', async () => {
+      const event = { name: 'John', age: 18 };
+      const middyfiedHandler = middy(handler).use(
+        parser({ schema: TestSchema })
+      );
+
+      expect(await middyfiedHandler(event, {} as Context)).toEqual(event);
+    });
+
+    it('should throw when the schema does not match', async () => {
+      const middyfiedHandler = middy(handler).use(
+        parser({ schema: TestSchema })
+      );
+
+      await expect(middyfiedHandler(42, {} as Context)).rejects.toThrow();
+    });
+
+    it('should throw when provided schema is invalid', async () => {
+      const middyfiedHandler = middy(handler).use(
+        parser({ schema: {} as ZodSchema })
+      );
+
+      await expect(
+        middyfiedHandler({ foo: 'bar' }, {} as Context)
+      ).rejects.toThrowError();
+    });
+  });
+});